import java.util.*;
public class Fenotipe
{
	private static final String DAYS[] ={"L","M","C","J","V","S","D"}; 
	//must have always two sets
	private HashSet<Integer> sets[] = new HashSet[2];
	//7 days, 3 types of truck per day
	private int trucks[][] = new int[7][3];
	
	private int dayMask;
	
	private double totalCost = Double.NaN;
	
	private boolean LOCAL_DEBUG = false;
	private double[] slack;
	public double[] getSlack()
	{
		return this.slack;
	}
	public Fenotipe(HashSet<Integer>[] sets, int[][] trucks, int dayMask)
	{
		this.sets = sets;
		this.trucks = trucks;
		this.dayMask = dayMask;
	}
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		StringBuilder s[] = new StringBuilder[2];
		s[0] = new StringBuilder();
		s[1] = new StringBuilder();
				
		for (int d = 0; d < 7; d++)
		{
			int ind_sb =  ((1<<d)&this.dayMask ) == 0  ? 0 : 1;
			s[ind_sb].append(DAYS[d]+"{"+trucks[d][0]+","+trucks[d][1]+","+trucks[d][2]+"} ");
		}
		
		sb.append("Rute 1 Buildings: "+sets[0]+"\n");
		sb.append("       days     : "+s[0].toString().trim()+"\n");
		sb.append("Rute 2 Buildings: "+sets[1]+"\n");
		sb.append("       days     : "+s[1].toString().trim()+"\n");
		
		return sb.toString();
	}
	/*
	 * Return a map with key equals to a day D, and value equal to the days where the garbage "accumulate" to be
	 * load on day D;
	 */
	public HashMap<Integer,HashSet<Integer> > getDayMap( final HashSet<Integer> recolectDaysGroupOne , 
			final HashSet<Integer> recolectDaysGroupTwo)
	{
		HashMap<Integer,HashSet<Integer> > m = new HashMap<Integer,HashSet<Integer> >();
		
		for (int day = 0; day < 7; day++)
			m.put(day,new HashSet<Integer>());
		
		for (int day : recolectDaysGroupOne)
		{
			m.get(day).add(day);
			int prev = day;
			while(true)
			{
				prev = prev == 0 ? 6 : prev - 1;
				if( recolectDaysGroupOne.contains(prev) || prev == day )break;
				m.get(day).add(prev);
			}
		}
		for (int day : recolectDaysGroupTwo)
		{
			m.get(day).add(day);
			int prev = day;
			while(true)
			{
				prev = prev == 0 ? 6 : prev - 1;
				if( recolectDaysGroupTwo.contains(prev) || prev == day )break;
				m.get(day).add(prev);
			}
		}
		return m;
	}
	/**
	 * Fitness function, returns 1 / (z + 1 + p) 	 
	 * where z = 	CostTruckType1 x TruckType1Count +
	 * 				CostTruckType2 x TruckType2Count + 
	 * 				CostTruckType3 x TruckType3Count + p
	 * where p is INF if the trucks can not load the amount of garbage times Main.SLACK_PROPORTION   
	 * @return
	 */
	public double getFitness()
	{	
		HashSet<Integer> recolectDaysGroupOne = new HashSet<Integer>();
		HashSet<Integer> recolectDaysGroupTwo = new HashSet<Integer>();
		for (int day = 0; day < 7; day++)
		{
			int dayGroup = ((1<<day)&dayMask) == 0 ? 0 : 1;							
			if( dayGroup == 0 )recolectDaysGroupOne.add(day);			//set 0
			else recolectDaysGroupTwo.add(day);// set 1
		}
		HashMap<Integer,HashSet<Integer> > m = getDayMap(recolectDaysGroupOne, recolectDaysGroupTwo);
		if(LOCAL_DEBUG)System.out.println("Group One Days: "+recolectDaysGroupOne);	
		if(LOCAL_DEBUG)for (int day : recolectDaysGroupOne)System.out.println("              "+day+": "+m.get(day));
		if(LOCAL_DEBUG)System.out.println("Group Two Days: "+recolectDaysGroupTwo);
		if(LOCAL_DEBUG)for (int day : recolectDaysGroupTwo)System.out.println("              "+day+": "+m.get(day));		
		if(LOCAL_DEBUG)System.out.println("Distribution  : "+m);
		
		double garbageAccum[] = new double[7];
		double matrix[][] = Data.getMatrix();
		
		for (int building = 0; building < matrix.length; building++)
			for (int day = 0; day < 7; day++)
				for (int dc : m.get(day))
					garbageAccum[day] += matrix[building][dc]*Data.SLACK_PROPORTION;
		
		if(LOCAL_DEBUG)System.out.println("    accum: "+Util.toString(garbageAccum));
		
		double garbageCapacityAccum[] = new double[7];
		for (int day = 0; day < 7; day++)
			for (int truckType = 0; truckType < 3; truckType++)
				garbageCapacityAccum[day] += Data.TRUCK_CAPACITY[truckType]*this.trucks[day][truckType];
		
		double trucksCost = 0; // z
		for (int day = 0; day < 7; day++)
			for (int truckType = 0; truckType < 3; truckType++)
				trucksCost += this.trucks[day][truckType]*Data.TRUCK_COST[truckType];
		
		
		if(LOCAL_DEBUG)System.out.println(" capacity: "+Util.toString(garbageCapacityAccum));
		if(LOCAL_DEBUG)System.out.println("TruckCost: "+Util.format(trucksCost));
		double dif[] = new double[7];
		for (int i = 0; i < dif.length; i++)
			dif[i] = garbageCapacityAccum[i]-garbageAccum[i];

		if(LOCAL_DEBUG)System.out.println("Slack    : "+Util.toString(dif));
		this.slack = dif;
		
		totalCost = trucksCost;
		return 1d / (trucksCost + 1 + getPenalty(garbageCapacityAccum, garbageAccum));
	}
	public double getFenotipeCost()
	{
		this.getFitness();
		return this.totalCost;
	}
	public double getFitnessDebug()
	{
		LOCAL_DEBUG = true;
		double ans = this.getFitness();
		LOCAL_DEBUG = false;
		return ans;
	}
	private double getPenalty( final double cap[] , final double garbageAccum[] )
	{
		for (int day = 0; day < 7; day++)
			if( garbageAccum[day] > cap[day])
				return Data.INF;
		double acum = 0;
		for (int day = 0; day < 7; day++)
			if( cap[day]-garbageAccum[day] > 0)
				acum += cap[day]-garbageAccum[day];
		return acum;
	}
	public int getDayMask()
	{
		return this.dayMask;
	}
	public int[][] getTrucks()
	{	
		return this.trucks;
	}
}
